﻿using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace WSU.ChekaParallel.TestThread.Coordinate
{
    static class TestBarrier
    {
        private static void Sample1()
        {
            const int TotalWorker = 4;

            // the second parameter of the constructor is a delegate
            // which will be invoked when barrier reaches zero but has not unblock all the threads yet
            // this is useful to some aggregation work in that delegate, because when this delegate is invoked
            // none thread is unblocked, so it is thread-safe
            using (Barrier barrier = new Barrier(TotalWorker, b => Console.WriteLine("*********************\n")))
            {
                Random rand = new Random();
                Task[] tasks = new Task[TotalWorker];

                for (int index = 0; index < TotalWorker; ++index)
                {
                    int sleepTime = rand.Next(100, 500);
                    int temp = index;
                    tasks[index] = Task.Factory.StartNew(() =>
                    {
                        for (int runtime = 0; runtime < 3; ++runtime)
                        {
                            Thread.Sleep(sleepTime);
                            Console.WriteLine("Task<{0}> finished {1}-th run", temp, runtime);
                            barrier.SignalAndWait();
                        } // for each run in single worker
                    });
                } // for each worker

                Task.WaitAll(tasks);
            }
        }

        /// <summary>
        /// chekanote: barrier is always used to implement a mutiple phase job
        /// in each phase, there will be multiple workers running in parallel
        /// and then compile at the end of that phase, the result of current phase
        /// will be used as the input of next phase
        /// </summary>
        private static void MultiPhaseJobWithBarrier()
        {
            int[] numbers = new int[5];
            Task[] tasks = new Task[numbers.Length];

            int total = 0;
            Barrier barrier = new Barrier(numbers.Length, b =>
            {
                total = numbers.Sum();
                Console.WriteLine("************ PHASE '{0}' TOTALLY COMPLETES ************", b.CurrentPhaseNumber);
            });

            Random random = new Random();
            for (int index = 0; index < numbers.Length; index++)
            {
                int taskNo = index;
                int sleepTime = random.Next(500, 2000);

                tasks[taskNo] = Task.Factory.StartNew(() =>
                {
                    numbers[taskNo] = taskNo;
                    Thread.Sleep(sleepTime);
                    Console.WriteLine("task<{0}> finishes phase<{1}>", taskNo, barrier.CurrentPhaseNumber);

                    barrier.SignalAndWait();

                    numbers[taskNo] -= total;
                    Thread.Sleep(sleepTime);
                    Console.WriteLine("task<{0}> finishes phase<{1}>", taskNo, barrier.CurrentPhaseNumber);
                });
            }

            Task.WaitAll(tasks);
            Console.WriteLine("\n************ ALL TASKS COMPLETES ************\n");

            for (int index = 0; index < numbers.Length; index++)
            {
                Console.WriteLine("result[{0}] = {1}", index, numbers[index]);
            }
        }

        /// <summary>
        /// this fault-recover technique is used when you want to continue without the result from that abandoned task
        /// </summary>
        private static void RemoveParticipantWhenError()
        {
            Barrier barrier = new Barrier(2, b => Console.WriteLine("######### phase<{0}> completes with {1} participants",
                    b.CurrentPhaseNumber, b.ParticipantCount));

            Task oktask = Task.Factory.StartNew(() =>
            {
                for (int index = 0; index < 3; index++)
                {
                    Thread.Sleep(300);
                    Console.WriteLine("OK task finishes phase<{0}>", index + 1);
                    barrier.SignalAndWait();
                }
            });

            Task errortask = Task.Factory.StartNew(() => { throw new InvalidOperationException("test"); })
                .ContinueWith(antecedent =>
                                  {
                                      Console.WriteLine("exception caught with message'{0}',remove participant",
                                          antecedent.Exception.InnerException.Message);
                                      barrier.RemoveParticipant();
                                  }, TaskContinuationOptions.OnlyOnFaulted);

            Task.WaitAll(oktask, errortask);
        }

        /// <summary>
        /// This technique works if you don’t want any of the Tasks to continue if any of them throw an exception
        /// </summary>
        private static void CancelAllWhenAnyError()
        {
            Barrier barrier = new Barrier(2);

            CancellationTokenSource cts = new CancellationTokenSource();

            Task oktask = Task.Factory.StartNew(() =>
                                                    {
                                                        for (int index = 0; index < 3; index++)
                                                        {
                                                            Thread.Sleep(100);
                                                            Console.WriteLine("OK task finishes phase<{0}>", index + 1);

                                                            try
                                                            {
                                                                barrier.SignalAndWait(cts.Token);
                                                            }
                                                            catch (OperationCanceledException)
                                                            {
                                                                Console.WriteLine("!!!!! oktask cancel waiting on barrier");
                                                                break;
                                                            }
                                                        }
                                                    });

            Task errortask = Task.Factory.StartNew(() =>
                                                       {
                                                           Thread.Sleep(300);
                                                           throw new InvalidOperationException("test");
                                                       })
                                                       .ContinueWith(antecedent =>
                                                       {
                                                           Console.WriteLine("exception caught with message'{0}',cancel all",
                                                               antecedent.Exception.InnerException.Message);
                                                           cts.Cancel();
                                                       }, TaskContinuationOptions.OnlyOnFaulted);

            Task.WaitAll(oktask, errortask);
        }

        public static void TestMain()
        {
            // Sample1();
            // MultiPhaseJobWithBarrier();
            // RemoveParticipantWhenError();
            CancelAllWhenAnyError();
        }// TestMain
    }
}